home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / admin / adduser next >
Encoding:
Korn shell script  |  1997-08-26  |  37.0 KB  |  1,245 lines

  1. #!/bin/ksh
  2. # adduser: script to add a user to the system.
  3. # @(#) adduser.ksh 3.0 97/06/27
  4. # 91/01/25 john h. dubois iii (john@armory.com)
  5. # 91/02/02 added interactive capability
  6. # 91/09/27 put phone num in GCOS field if given, cleaned up
  7. # 91/10/27 Changed format of address record written.
  8. #          Also, now writes address record to first word of $ADDRESS
  9. # 92/03/07 changed mkuser.init invokation from execution to sh sourcing
  10. #          since the files are not executable under UNIX.
  11. #          Changed to use vars from /etc/default/authsh if no
  12. #          /etc/default/mkuser
  13. # 92/03/20 Added code to use /etc/shadow if it exists
  14. #          Run authck -p if available
  15. # 92/04/03 Use MIN_SUGGEST_UID, MIN_SUGGEST_GID, and LOGIN_GROUP throughout
  16. #          script so that they can be changed in mkuser.defs file
  17. # 92/05/03 Move automatic uid generation after reading of mkuser.defs
  18. # 93/05/18 Let $ADDRESS be separated by colons in addition to whitespace
  19. # 93/07/09 Print correct warning if username is a mail alias.
  20. # 93/11/14 Print warning if password is truncated by makepass.
  21. # 93/12/16 Modified to use /tcb/bin/ap instead of doing everything dangerously
  22. # 93/12/31 Moved all vars specific to an account into associative arrays.
  23. #          Added ability to process multiple account requests.
  24. # 94/01/02 Check whether multiple values are given for an account parameter.
  25. # 94/01/06 Print warnings at end for failed account creations & truncated
  26. #          passwords.
  27. # 94/01/09 Moved group adding into ap processing.  Moved ap and mkuser.init
  28. #          invokation to end of script.  This allows all accounts to be
  29. #          created by a single instance of ap.
  30. # 94/04/16 Exit if ap fails.
  31. # 94/05/22 Use default address file if $ADDRESS not set.
  32. # 94/09/12 Added -n option
  33. # 94/12/10 Deal with phone numbers much better.  
  34. #          Discard + chars before writing record to address database.
  35. #          Capitalize 1st char of names if they were entered all lower case.
  36. #          Require that ap_tmp file have nonzero size before running ap etc.
  37. # 94/12/16 Phone num canonicalization bugfix
  38. # 95/07/08 Fix to tr bug in above fix
  39. # 95/07/28 Tell where ap file left if it fails.
  40. # 95/08/24 Get rid of extra spaces in names
  41. # 95/10/20 Use namespaces instead of associative arrays to speed things up
  42. # 95/12/23 Use egrep, not /bin/egrep; new OS doesn't have a /bin/egrep
  43. # 96/01/20 Make tempfile start with #; create it more safely
  44. # 96/03/02 Set BadAccounts correctly.
  45. # 96/03/17 Added x option.
  46. # 96/03/20 Use sed instead of tr when ranges are needed because they have
  47. #          to be specified differently in different releases.
  48. # 96/04/27 Remove tmpfile on error exit
  49. # 96/05/10 Deal with all-lower-case names too.
  50. # 97/06/27 Make filename given on command line work again.
  51. #
  52. # This script adds a user to a XENIX or UNIX system (with "traditional" or
  53. # lower security) through the 'ap' utility.
  54. # This script runs /usr/lib/mkuser/<shell>/mkuser.init to do the following:
  55. #  create user's home directory
  56. #  copy login files to user's home directory
  57. #  send the user welcome mail
  58.  
  59. # Bugs:
  60. # Password is echoed when used interactively.
  61. # If ap is not used, no attempt is made to lock passwd or group file
  62. # (should use ale(ADM) to lock them)
  63. # UIDs are reused, which is a security risk unless all old files, IPC objects,
  64. # processes, etc. on the system that are owned by the uid are removed.
  65. # uucp/mkuser.defs has MIN_SUGGEST_UID=100 in it, but it is ignored.
  66.  
  67. # @(#) mktempfile 1.0 96/05/22
  68. # 96/01/20 jhdiii
  69.  
  70. # Usage: mkfiles name ...
  71. # Creates the named files with some attempt at security.
  72. # This will be more reliable if user do not have chown authority.
  73. # Any file that contains no / characters is created in the user's tempdir.
  74. # If TMP was not set, it is set by this function.
  75. # Returns 0 on success; prints a diagnostic message & returns 1 on failure.
  76. function mkfiles {
  77.     typeset file files
  78.     typeset -i i=0
  79.  
  80.     : ${TMP:=$TMPDIR}
  81.     : ${TMP:=/tmp}
  82.     for file; do
  83.     [[ "$file" != */* ]] && file="$TMP/$file"
  84.     files[i]=$file
  85.     let i+=1
  86.     done
  87.     set -- "${files[@]}"
  88.  
  89.     rm -f "$@" || {
  90.     # hopefully rm will have printed a more specific message.
  91.     print -u2 "Could not remove pre-existing files."
  92.     return 1
  93.     }
  94.     for file; do
  95.     # Use >> to avoid 0'ing the file in case a symlink was just made from
  96.     # the filename to something we don't want to truncate
  97.     >> "$file" || {
  98.         print -u2 "Could not create temp file $file"
  99.         return 1
  100.     }
  101.     # These are very suspicious
  102.     [ -s "$file" ] && {
  103.         print -u2 "New tempfile is not empty!!!"
  104.         return 1
  105.     }
  106.     [ -O "$file" ] || {
  107.         print -u2 "Do not own $file!!!"
  108.         return 1
  109.     }
  110.     done
  111.     # Could do some more stuff here, but anyone concerned with security should
  112.     # have chown authorization off for most users.
  113.     return 0
  114. }
  115.  
  116. # Usage: mktempfile name
  117. # Creates a tempfile name tempdir/#name$$, and sets the global mktempfile_ret
  118. # to that name.  tempdir is a temp directory in $TMP, $TMPDIR, or /tmp, and $$
  119. # is the PID of the current process.
  120. # name should be 8 characters or less, so that the resulting filename will
  121. # not be more than 14 characters long (a limit on some machines).
  122. # Returns 0 on success, prints a diagnostic message & returns 1 on failure.
  123. function mktempfile {
  124.     typeset file="#$1$$"
  125.     mkfiles "$file"
  126.     mktempfile_ret="$TMP/#$1$$"
  127. }
  128.  
  129. ## Start of nvar lib
  130.  
  131. # separate-namespace routines
  132. # 95/10/20 john h. dubois iii (john@armory.com)
  133. # These routines store values in variables whose namespace is separated
  134. # from other variables by preceding them with a prefix.
  135. # prefixes must have names that are valid shell variable names.
  136.  
  137. # Usage: nStore <prefix> <varname> <value> <append>
  138. # Stores value <value> in the <varname> that is part of namespace <prefix>
  139. # A null <value> may be given.
  140. # If a 4th argument is given, the value is appended to the current value
  141. # stored for the variable (if any).
  142. # Return value is 0 for success,
  143. # 2 for failure due to bad varname, 3 for bad syntax
  144. function nStore {
  145.     typeset var="$1_$2" Val=$3
  146.  
  147.     [ "$var" = _ ] && return 2
  148.     case $# in
  149.     3) eval $var=\$Val;;
  150.     4) eval $var=\"\$$var\$Val\";;
  151.     *) return 3;;
  152.     esac
  153.     return 0
  154. }
  155.  
  156. # Usage: nGet [-n] <prefix> <var> <result>
  157. # Gets the value of var <var> in namespace <prefix> and stores it in shell
  158. # variable <result>.  Returns failure status if the value was null.
  159. # If -n is given, <result> is not touched in the value is null.
  160. # result may have the form array[n].
  161. # If <result> is not given, nothing is assigned; only the return status is set.
  162. function nGet {
  163.     typeset noTouch=false var result
  164.     if [ "$1" = -n ]; then
  165.     noTouch=true
  166.     shift
  167.     fi
  168.     var="$1_$2"
  169.     result=$3 
  170.  
  171.     eval r=\$$var
  172.     [[ $# -eq 3 && ( $noTouch = false || -n "$r" ) ]] && eval $result=\$r
  173.     [ -n "$r" ]
  174. }
  175.  
  176. # Usage: nUnset <prefix> <var> ...
  177. # Each variable <var> in namespace <prefix> is unset.
  178. function nUnset {
  179.     typeset prefix=${1}_ var
  180.  
  181.     shift
  182.     for var; do
  183.     eval unset $prefix$var
  184.     done
  185. }
  186.  
  187. # Usage: nGetMany <array> <prefix> <varname> ...
  188. # Each of the named variables in namespace <prefix> is stored in shell array
  189. # <arry> with indices starting with 0.
  190. # The number of variables that had non-null values is returned (it will
  191. # wrap around if more than 255 values are stored).
  192. function AGetMany {
  193.     typeset -i NumNonNull=0 ind=0
  194.     typeset Arr=$1 prefix=$2 var
  195.  
  196.     shift 2
  197.     for var; do
  198.     nGet $prefix "$var" "$Arr[$ind]" && let NumNonNull+=1
  199.     let ind+=1
  200.     done
  201.     return $NumNonNull
  202. }
  203.  
  204. # set_vars: store variable assignments in a namespace.
  205. # 93/12/28 John H. DuBois III (john@armory.com)
  206. # Usage: set_vars prefix [filename ...]
  207. # where the lines in filename (or the input) are of the form
  208. # var=value
  209. # value may contain whitespace, backslashes, quote characters, etc.;
  210. # they will become part of the value assigned.
  211. # Lines that begin with a # (optionally preceded by whitespace),
  212. # lines that do not contain a '=', and lines that contain illegal characters
  213. # in the var part are ignored.
  214. # The only legal characters are [a-zA-Z0-9_]
  215. # Values are stored in variables of the name given before the =, preceded by
  216. # prefix.  prefix may be given as a null string.
  217. # Example: 'set_vars foo_ bar' will read the file bar.  If bar contains the line
  218. # blit=xyz
  219. # then this shell variable assignment will be made:
  220. # foo_blit=xys
  221.  
  222. function set_vars {
  223.     typeset prefix digits
  224.  
  225.     prefix=$1
  226.     # If prefix is non-null, first char of name may be a digit
  227.     [ -n "$prefix" ] && digits=0-9
  228.     shift
  229.     for file; do
  230.     if [ ! -f "$file" ]; then
  231.         print -u2 -- "$file: No such file."
  232.         return 1
  233.     fi
  234.     if [ ! -r "$file" ]; then
  235.         print -u2 -- "$file: Could not open for reading."
  236.         return 1
  237.     fi
  238.     done
  239.     # return exit status of eval
  240.     # Delete lines that start with # or which do not contain an =
  241.     # or which try to assign to bad var names
  242.     # Quote '
  243.     # Put new ' around value
  244.     eval "$(sed "
  245. /^[     ]*#/d
  246. /^[a-zA-Z_$digits][a-zA-Z0-9_]*=/!d
  247. s/'/'\\\\''/g
  248. s/=/='/
  249. s/$/'/
  250. s/^/$prefix/" "$@")"
  251. }
  252.  
  253. # Usage: nset_vars <prefix> [filename ...]
  254. # Any legal vars assigned to in the given files are stored in namespace <prefix>
  255. function nset_vars {
  256.     typeset prefix=${1}_
  257.  
  258.     shift
  259.     set_vars $prefix "$@"
  260. }
  261.  
  262. # Usage: nChkStore <prefix> <varname> <value>
  263. # Exit if <varname> is already set in namespace <prefix>, else set it to <value>
  264. function ChkStore {
  265.     typeset prefix=$1 var=$2 value=$3
  266.  
  267.     if nGet $prefix $var; then
  268.     print -u2 "Error: $index already set.  Exiting."
  269.     exit 1
  270.     else
  271.     nStore $prefix $var "$value"
  272.     fi
  273. }
  274.  
  275. ## End of nvar lib
  276.  
  277. # Print help message.
  278. # Globals used: Name, Usage, PAGER, MIN_SUGGEST_UID, LOGIN_GROUP
  279. # Globals changed: none.
  280. function help {
  281. echo \
  282. "$Name: add a user to the system.
  283. $Usage
  284. If a filename is given, it is read for the neccessary information. Otherwise,
  285. if the input is a tty, the neccessary information is prompted for
  286. interactively.  If the input is not a tty, the input is read for the
  287. information.  In all cases except for interactive operation, the input read is
  288. expected to consist of lines of the form
  289. var=value
  290. where value gives an appropriate value for var.
  291. Each var should be one of the following (unrecognized vars are ignored):
  292.  
  293. acctname:  Name of the account
  294. password:  Initial password
  295. uid:       Numeric user id
  296. group:     Login group (numeric or name)
  297. name:      Name/comment for the GCOS (comment) field of /etc/passwd
  298. shell:     Login shell
  299. address:   Address for (optional) address database
  300. phone_num: Phone number for the GCOS field and (optionally) address database
  301.  
  302. acctname, name, and shell are required.
  303. If multiple groups of lines that contain '=' characters are read,
  304. separated by lines that do not contain '=' characters,
  305. each group is processed as a separate account request.
  306. A directory with the name of shell with the files required by mkuser
  307. must exist in /usr/lib/mkuser.
  308. If password is not given, the account is created without a password.
  309. If a password of '*' is given, a literal '*' will be put in the user's
  310. password field, so that no password will give access to the account.
  311. If a normal password is given, it is encrypted with the \"makepass\" utility.
  312. If \"makepass\" is not available, a null or '*' password should be given,
  313. and the user's password set with \"passwd\".
  314.  
  315. The default for uid is the first unused uid that is MIN_SUGGEST_UID
  316. (currently configured at $MIN_SUGGEST_UID) or higher.
  317. The default for group is \"$LOGIN_GROUP\".
  318. If $Name is being used non-interactively,
  319. the default or given group must already exist in /etc/group.
  320.  
  321. The defaults for address and phone_num are null.
  322. If an address was given for the user, then after the user has been created
  323. an address record is written.  The record is in the format
  324. Name
  325. Address
  326. Phone number
  327. Email address
  328. +
  329. If the environment variable ADDRESS is set, the address record is written to
  330. the file given by the first word of its value.  Otherwise, it is written to
  331. the file $addrdef, if it exists.  If not, it is written to the
  332. standard output.
  333.  
  334. Normally, both informational and error messages are printed to the error output.
  335. If stderr is not a tty, only error messages are printed.
  336. 'ap' is run to add a user to the tcb database.
  337. Note: very little sanity checking of parameters is done.
  338. Options:
  339. -a: Do not write an address record.
  340. -t: Test: the normal messages are printed, but the user is not actually added
  341.     to the system.  Informational messages are printed even if stderr is not
  342.     a tty.
  343. -x: Turn on debugging.  Informational messages are printed even if stderr is
  344.     not a tty, and extra information is printed.
  345. -r<num>:  The number of login retries before the account is locked is set to
  346.     <num> for each account created.
  347. -h: Print this help.
  348. -n: Set non-interactive mode (expecting input of the form var=value) even if
  349.     input is a tty." | ${PAGER:-more}
  350. }
  351.  
  352. # Test whether an integer variable is nonzero or zero
  353. alias istrue="test 0 -ne"
  354. alias isfalse="test 0 -eq"
  355.  
  356. # DoFlags: interpret command line args
  357. # Usage: DoFlags "$@"
  358. # Globals used: $*, Usage
  359. # Globals set: test, debug, Interactive, Retries
  360. # returns number of args that were options
  361. function DoFlags {
  362.     typeset opt
  363.     typeset -i i
  364.  
  365.     while getopts :ahntr:x opt; do
  366.     case $opt in
  367.     a)
  368.         DoAddr=0;;
  369.     t)
  370.         test=1
  371.         debug=1
  372.         ;;
  373.     x)
  374.         set -x
  375.         debug=1
  376.         ;;
  377.     n)
  378.         Interactive=0;;
  379.     h)
  380.         help
  381.         exit 0;;
  382.     r)
  383.         if i=$OPTARG && [ i -gt 0 ]; then
  384.         Retries="u_maxtries#$i:"
  385.         else
  386.         print -u2 "$Name: Bad value given for max retries."
  387.         exit 1
  388.         fi;;
  389.     +?)
  390.         print -u2 "$Name: options should not be preceded by a '+'."
  391.         exit 1
  392.         ;;
  393.     :)
  394.         print -r -u2 -- \
  395.         "$Name: Option '$OPTARG' requires a value.  Use -h for help."
  396.         exit 1
  397.         ;;
  398.     ?) 
  399.         print -u2 "$Name: $OPTARG: bad option.  Use -h for help."
  400.         exit 1
  401.         ;;
  402.     esac
  403.     done
  404.  
  405.     # remove args that were options
  406.     let OPTIND=OPTIND-1
  407.     return $OPTIND
  408. }
  409.  
  410. # GetShells: sets Shells to be a list of available shells
  411. # Globals set: Shells
  412. function GetShells {
  413.     typeset s
  414.  
  415.     cd /usr/lib/mkuser || {
  416.     print -u2 "Could not change dir to /usr/lib/mkuser.  Exiting."
  417.     exit 1
  418.     }
  419.     for s in */mkuser.init; do
  420.     Shells="$Shells   ${s%/mkuser.init}"
  421.     done
  422. }
  423.  
  424. # GetGroups: set Groups to be a list of groups
  425. # Globals set: Groups
  426. function GetGroups {
  427.     set -- $(wc -l /etc/group)
  428.     if [ $1 -gt 22 ]; then
  429.     # If more than 22 groups, print only group name & remove newlines
  430.     set -- $(sed 's/:.*//' /etc/group)
  431.     Groups=$*
  432.     else
  433.     Groups=$(sed 's/:[^:]*:/    /;s/:/    /;s/,/ /g' /etc/group)
  434.     fi
  435. }
  436.  
  437. # Globals set:
  438. # vars in namespace Account
  439. function GetAccountInfo {
  440.     typeset passwdquery vars var invar
  441.  
  442.     GetShells    # set Shells to list of available shells
  443.     GetGroups    # set Groups to be a list of available groups
  444.  
  445.     if whence makepass > /dev/null; then
  446.     passwdquery="Enter the new user's password (note: passwd will echo)"
  447.     else
  448.     passwdquery=\
  449. "Press return for a null password, or enter '*' to prevent logins"
  450.     fi
  451.  
  452.     vars="acctname name shell password uid group address phone_num"
  453.     set -- \
  454. "Enter the new user's login name" \
  455. "Enter the new user's name (the comment to put in /etc/passwd)" \
  456. "Enter the new user's login shell.  The available shells are:
  457. $Shells" \
  458. "$passwdquery" \
  459. "Enter the new user's uid (or press return to use the next available uid)" \
  460. "Enter the new user's login group, or press return to use the default group
  461. ($LOGIN_GROUP).  The currently existing groups are:
  462. $Groups" \
  463. "Enter the new user's address" \
  464. "Enter the new user's phone number"
  465.  
  466.     for var in $vars; do
  467.         print -n -u2 "$1\n$var="
  468.         read invar
  469.         nStore Account $var "$invar"
  470.     until Test_$var; do
  471.         print -n -u2 "$1\n$var="
  472.         read invar
  473.         nStore Account $var "$invar"
  474.     done
  475.         shift
  476.     done
  477. }
  478.  
  479. # Prints next unused uid
  480. # Usage: SetUID min-uid [skip-uids]
  481. function SetUID {
  482.     typeset UIDFound minuid=$1
  483.  
  484.     shift
  485.     awk -F: -v uid=$minuid -v UIDsUsed="$*" '
  486.     uids[$3]
  487. }
  488. END {
  489.     split(UIDsUsed,Elem," +")
  490.     for (i in Elem)
  491.     uids[Elem[i]]
  492.     while (uid in uids)
  493.     uid++
  494.     print uid
  495. }' /etc/passwd | read UIDFound
  496.     print $UIDFound
  497. }
  498.  
  499. # Check account parameters
  500. # In Account[], sets groupname and gid; may unset name; may rewrite phone_num,
  501. # may remove path component from shell; sets encpass to encoded password
  502. # Error message is printed & 1 is returned if certain tests fail.
  503. # For non-interactive account creation.
  504. function CheckParams {
  505.     istrue debug && set -x
  506.     Test_acctname || return 1
  507.     Test_password || return 1
  508.     nStore Account encpass "$encpass"
  509.     Test_uid || return 1
  510.     Test_group || return 1
  511.     Test_name || return 1
  512.     Test_shell || return 1
  513.     Test_phone_num || print -u2 "Phone number not set."
  514.     return 0
  515. }
  516.  
  517. # Checks whether account already exists in /etc/passwd or is a mail alias
  518. # Returns 1 of so; otherwise 0
  519. # Prints err messages if name exists
  520. function Test_acctname {
  521.     istrue debug && set -x
  522.     typeset accountname
  523.  
  524.     nGet Account acctname accountname
  525.  
  526.     if egrep "^$accountname:" /etc/passwd >| /dev/null; then
  527.     print -u2 -r "Account $accountname already exists."
  528.     return 1
  529.     fi
  530.     (/usr/mmdf/bin/malias $accountname | egrep 'no aliases') \
  531.     > /dev/null 2>&1 || 
  532.     print -u2 "Warning: $accountname is a mail alias.  Creating account anyway."
  533.     return 0
  534. }
  535.  
  536. # Checks whether password is a valid password
  537. # Returns 0 if so; otherwise nonzero
  538. # Side effects: sets "encpass" to encoded password
  539. # (or * or nothing, if that's what password is)
  540. # Prints err message if bad password
  541. # Global vars: Account*, encpass
  542. function Test_password {
  543.     typeset password
  544.     typeset accountname
  545.  
  546.     nGet Account acctname accountname
  547.     nGet Account password password
  548.  
  549.     MakePass "$password" "$accountname"
  550. }
  551.  
  552. # Checks whether $uid already exists in /etc/passwd
  553. # Returns 0 if it doesn't or is null, 1 if it does
  554. # Prints err message if uid is already used
  555. # Global vars: Account*
  556. function Test_uid {
  557.     typeset uid
  558.  
  559.     nGet Account uid uid
  560.  
  561.     [ -z "$uid" ] && return 0
  562.     if [[ "$uid" != [0-9]* ]]; then
  563.     print -u2 -r "Bad uid $uid."
  564.     return 1
  565.     fi
  566.  
  567.     if egrep "^[^:]*:[^:]*:$uid:" /etc/passwd >| /dev/null; then
  568.     print -u2 -r "uid $uid already assigned."
  569.     return 1
  570.     fi
  571.     return 0
  572. }
  573.  
  574. # Usage: Test_group
  575. # Checks whether groupname/gid <group> exists in /etc/group
  576. # If non-interactive, returns 1 if it doesn't, 0 if it does
  577. # If interactive and group does not exist, gives user the option
  578. # of creating the group.  If the option is rejected or group
  579. # creation fails, returns 1; otherwise returns 0.
  580. # Side effects:
  581. # If found, sets group to group name and gid to group id.
  582. # If interactive, may create a group by adding a line to /etc/group
  583. # If non-interactive, prints an error message if group does not exist
  584. # Global vars: $LOGIN_GROUP (default group id/name), Account*
  585. function Test_group {
  586.     typeset group groupname gid
  587.  
  588.     nGet Account group group
  589.  
  590.     if [ -z "$group" ]; then
  591.     if [ -z "$LOGIN_GROUP" ]; then
  592.         print -u2 "\007\007\007ERROR in Test_group: LOGIN_GROUP not set."
  593.         exit 1
  594.     fi
  595.     group=$LOGIN_GROUP
  596.     fi
  597.     GoodGroup "$group" | read groupname gid
  598.     [ -z "$groupname" ] && return 1
  599.     nStore Account groupname "$groupname"
  600.     nStore Account gid "$gid"
  601.     return 0
  602. }
  603.  
  604. # Usage: GoodGroup <group>
  605. # Checks whether groupname/gid <group> exists in /etc/group
  606. # If non-interactive, returns 1 if it doesn't, 0 if it does
  607. # If interactive and group does not exist, gives user the option
  608. # of creating the group.  If the option is rejected or group
  609. # creation fails, returns 1; otherwise returns 0.
  610. # Side effects:
  611. # If found, prints groupname & gid
  612. # If interactive, may create a group by adding a line to /etc/group
  613. # If non-interactive, prints an error message if group does not exist
  614. function GoodGroup {
  615.     typeset group=$1
  616.  
  617.     [ -z "$group" ] && return 1
  618.     if FindGroup "$group"; then :; else
  619.     print -u2 "Group $group does not exist."
  620.     if ((Interactive)); then
  621.         MakeGroup $group || return 1
  622.     else
  623.         return 1
  624.     fi
  625.     fi
  626.     print "$groupname" "$gid"
  627.     return 0
  628. }
  629.  
  630. # Tests whether $name is a valid name that can be put in the GCOS field
  631. # Returns 0 if it is, 1 if not
  632. # Global vars: none
  633. function Test_name {
  634.     typeset name newName=
  635.     typeset -u -L1 u    # 1 char, upper case
  636.     typeset -l Comp    # lower case
  637.  
  638.     set -o noglob    # make sure globbing is off; local to this func
  639.     nGet Account name name
  640.  
  641.     if [[ "$name" = *:* ]]; then
  642.     print -u2 -r "Name/comment \"$name\" contains a ':'."
  643.     nStore Account name ""
  644.     return 1
  645.     fi
  646.     # Get rid of extra spaces
  647.     set -- $name
  648.     name="$*"
  649.     # If name was entered all in lower or upper case, convert the first char of
  650.     # each component to upper case & the rest to lower case
  651.     if [[ "$name" != *[A-Z]* ]]; then
  652.     for Comp in $name; do
  653.         u=$Comp
  654.         newName="$newName $u${Comp#?}"
  655.     done
  656.     nStore Account name "${newName#?}"
  657.     fi
  658.     return 0
  659. }
  660.  
  661. # Tests whether phone_num is a valid phone number that can be put in
  662. # the GCOS field
  663. # Returns 0 if it is, 1 if it isn't
  664. # Side effects: 
  665. # Rewrites NA phone numbers in format suitable for GCOS field: 
  666. # aaa/pppnnnn or ppp-nnnn
  667. # No - between prefix and number if area code is given, because SCO finger
  668. # daemon limits "office" field (where the phone # is put) to 11 chars.
  669. # Rewrites non-NA phone numbers to exclude invalid chars.
  670. # If phone_num isn't valid, sets it to null string and prints an error message
  671. # Global vars: Account*
  672. # 94/12/10 jhdiii  Rewrote.
  673. function Test_phone_num {
  674.     typeset phone_num Digits
  675.     typeset -i NumDig
  676.  
  677.     nGet Account phone_num phone_num
  678.  
  679.     [ -z "$phone_num" ] && return 0
  680.  
  681.     # Use sed instead of tr because tr behaviour re needing
  682.     # [] around ranges is incompatible between releases.
  683.     Digits=$(print -r -- "$phone_num" | sed 's/[^0-9]//g')
  684.     NumDig=${#Digits}
  685.  
  686.     # This test for whether a phone number is a NA number is based on 600
  687.     # phone numbers entered with account requests.  It will fail for numbers
  688.     # given with phone extensions, descriptions, numbers given with some digits
  689.     # as alpha characters, more than one phone number given, etc.
  690.     # optional-whitespace
  691.     # optional area code
  692.     #     optional leading 1
  693.     #         optional hyphen after 1
  694.     #     optional open-paren
  695.     #     3 digits
  696.     #     optional close-paren
  697.     #     any number of nondigits
  698.     # 3 digit prefix
  699.     # any number of nondigits
  700.     # 4 digit number
  701.     # optional whitespace
  702.     if print -r -- "$phone_num" | egrep '^ *((1-?)?\(?[0-9][0-9][0-9]\)?[^0-9]*)?[0-9][0-9][0-9][^0-9]*[0-9][0-9][0-9][0-9] *$' > /dev/null; then
  703.     # NA number
  704.     case $NumDig in
  705.     7) NewNum="${Digits%????}-${Digits#???}";;
  706.     10) NewNum="${Digits%???????}/${Digits#???}";;
  707.     11) NewNum="${Digits%???????}/${Digits#???}"
  708.         NewNum=${NewNum#1}
  709.         ;;
  710.     *)
  711.         print -u2 "Phone number test failed?!"
  712.         ;;
  713.     esac
  714.     elif [ NumDig -lt 10 ]; then
  715.     print -u2 "Invalid phone number: $phone_number"
  716.     NewNum=
  717.     else
  718.     # Get rid of any chars that should not exist anywhere in a phone #
  719.     NewNum=$(print -r -- "$phone_num" | sed 's/[^0-9/(),+. -]//g')
  720.     NewNum=${NewNum##*([!(+0-9])}
  721.     NewNum=${NewNum%%*([!0-9])}
  722.     [ ${#NewNum} -ne ${#phone_num} ] && print -u2 \
  723. "Discarded invalid chars in phone number '$phone_num';
  724. new number is: $NewNum"
  725.     fi
  726.  
  727.     nStore Account phone_num "$NewNum"
  728.     [ -z "$NewNum" ]
  729. }
  730.  
  731. # Tests whether shell is a valid shell
  732. # Returns 0 if it is, 1 if it isn't
  733. # Side effects: Removes any path component in shell
  734. # If shell is not valid, prints error message
  735. function Test_shell {
  736.     typeset shell
  737.  
  738.     nGet Account shell shell
  739.  
  740.     # ignore any attempt at full path name in shell
  741.     shell=${shell##*/}
  742.     nStore Account shell "$shell"
  743.     if [ ! -f /usr/lib/mkuser/$shell/mkuser.init ]; then
  744.     print -u2 -r "Don't know how to create a user with shell \"$shell\"."
  745.     return 1
  746.     fi
  747. }
  748.  
  749. # Stub
  750. function Test_address {
  751.     return 0
  752. }
  753.  
  754. # FindGroup [gid|groupname] 
  755. # searches /etc/group for a group with either the name or gid given
  756. # in the argument.
  757. # If found, sets groupname to group name and gid to group id and returns
  758. # status 0.
  759. # If fails, returns nonzero.
  760. # Globals used/modified: none.
  761. function FindGroup {
  762.     typeset gname gpass rgid gusers
  763.     typeset IFS=:
  764.     unset gname
  765.  
  766.     sed -n "/^$1:/p;/^[^:]*:[^:]*:$1:/p" /etc/group | 
  767.     read gname gpass rgid gusers
  768.     if [ -n "$gname" ]; then
  769.     groupname=$gname
  770.     gid=$rgid
  771.     return 0
  772.     else
  773.     return 1
  774.     fi
  775. }
  776.  
  777. # Makes a group.  Returns nonzero on failure.
  778. # Usage: MakeGroup gid|groupname
  779. # Global vars:
  780. # Sets gid and groupname.
  781. # May create a new group in /etc/group.
  782. # This function requires that the standard input & standard error streams
  783. # be connected to an interactive session.
  784. function MakeGroup {
  785.     typeset g=$1 badreply=true groupmade
  786.  
  787.     while [ $badreply ]; do
  788.     print -n -u2 "Create it, Select another group name or Abort? [c/s/a] "
  789.     read reply || return 1
  790.     case $reply in
  791.     a*) return 1;;
  792.     s*) print -u2 -n "Enter a group name or number: "
  793.         read g || return 1;;
  794.     c*) ;;
  795.     *) continue;
  796.     esac
  797.     unset badreply
  798.     done
  799.     groupmade=
  800.     while [ -z "$groupmade" ]; do
  801.     if [[ "$g" = *([0-9]) ]]; then
  802.         gid=$g
  803.         print -u2 -n "Enter a group name: "
  804.         read groupname || return 1
  805.         if egrep "^$groupname:" /etc/group >| /dev/null; then
  806.         print -u2 "Group name $groupname is already in use."
  807.         else
  808.         group=$groupname
  809.         if [ -n "$gid" ]; then
  810.             groupmade=true
  811.         else
  812.             g=$groupname
  813.         fi
  814.         fi
  815.     else
  816.         groupname=$g
  817.         print -u2 -n \
  818. "Enter a group id #, or press <return> to select the next available gid: "
  819.         read gid || return 1
  820.         if [ -n "$gid" ]; then
  821.         if egrep "^[^:]*:[^:]*:$gid:" /etc/group >| /dev/null; then
  822.             print -u2 "gid $gid is already in use."
  823.         else
  824.             groupmade=true
  825.         fi
  826.         else
  827.         # set gid to next unused gid
  828.         gid=`awk -F: '
  829.         { 
  830.             gids[$3]
  831.         }'"
  832.         END { 
  833.             for (gid = $MIN_SUGGEST_GID; gid in gids; gid++)
  834.             print gid
  835.         }
  836.         " /etc/group`
  837.         print -u2 "Group assigned gid $gid."
  838.         groupmade=true
  839.         fi
  840.     fi
  841.     done
  842.     print -u2 "Adding the following line to /etc/group:\n$groupname::$gid:"
  843.     isfalse test && echo "$groupname::$gid:" >> /etc/group
  844.     return 0
  845. }
  846.  
  847. # MakePass: set encpass to encoded version of $1
  848. # If password is '*' or null, make it the password without encrypting
  849. # Globals changed: encpass, Truncated
  850. # Usage: MakePass encoded-password accountname
  851. function MakePass {
  852.     if [[ "$1" = @(\\*|) ]]; then
  853.     encpass="$1"
  854.     else
  855.     encpass=`print -r "$1" | makepass` ||
  856.     { print -u2 "Bad password."; return 1; }
  857.     # If password is more than 8 chars, encoded password should be
  858.     # more than 13 chars.
  859.     if [[ "$1" = ?????????* && "$encpass" != ??????????????* ]]; then
  860.         print -u2 \
  861.         "\007\007\007***    Warning! Password truncated to 8 characters."
  862.         Truncated="$Truncated $2"
  863.     fi
  864.     fi
  865.     return 0
  866. }
  867.  
  868. # SetMkuserDef: set vars from an account defaults file
  869. # Usage: SetMkuserDef prefix filename ...
  870. # prefix_vars set:
  871. # HOME HOMEMODE PROFMODE MAILMODE LOGIN_GROUP
  872. # MIN_SUGGEST_UID MIN_SUGGEST_GID OTHER_GROUPS
  873. # If -d is given, set defaults for values not given
  874. # Returns 0 if file cannot be read
  875. function SetMkuserDef {
  876.     typeset file prefix var
  877.     typeset -i DefOK=0 SetDef=0
  878.     typeset VarList="HOME HOMEMODE PROFMODE MAILMODE LOGIN_GROUP
  879.     MIN_SUGGEST_UID MIN_SUGGEST_GID OTHER_GROUPS SHELL HOME_MODE HOME_DIR"
  880.     typeset $VarList
  881.  
  882.     if [ "$1" = -d ]; then
  883.     SetDef=1
  884.     shift
  885.     fi
  886.     prefix=$1
  887.  
  888.     for file; do
  889.     if [ -r $file ]; then
  890.         nset_vars $prefix "$file"
  891.         DefOK=1
  892.         break
  893.     fi
  894.     done
  895.     if isfalse DefOK; then
  896.     print -u2 "Could not read: $*"
  897.     return 1
  898.     fi
  899.  
  900.     # Set default & alternate values
  901.     for var in HOME_DIR HOME_MODE LOGIN_GROUP; do
  902.     nGet -n $prefix $var $var
  903.     done
  904.     # Set pairs of <var-name>,<alternate-value>
  905.     set -- HOME "$HOME_DIR" HOMEMODE "$HOME_MODE" OTHER_GROUPS "$LOGIN_GROUP"
  906.     istrue SetDef && set -- "$@" PROFMODE 600 MAILMODE 600 LOGIN_GROUP group \
  907.     MIN_SUGGEST_UID 200 MIN_SUGGEST_GID 50
  908.     while [ $# -gt 0 ]; do
  909.     # For each var, if it is not set and the alternate var is, set the
  910.     # var to the value of the alternate var.
  911.     nGet $prefix $1 || {
  912.         [ -n "$2" ] && nStore $prefix $1 "$2"
  913.     }
  914.     shift 2
  915.     done
  916.     return 0
  917. }
  918.  
  919. # AddTCBap: Add an account profile record to a file to be processed by ap
  920. # File passed to ap looks like this:
  921. # blort:x:450:50:Todd:/u/blort:/bin/ksh
  922. # blort:u_name=blort:u_id#450:\
  923. #     :u_pwd=x.5wL0kjhWqk.:\
  924. #     :u_type=general:u_lock@:chkent:
  925. # Usage: AddTCBap acctname encpass uid gid GCOS home shell [group ...]
  926. # Global data used:
  927. # debug        nonzero if debugging info should be printed
  928. # ap_tmp    file to write record to
  929. # Retries: Max login retries field, if any.  Should be in the form:
  930. # u_maxtries#n:
  931. function AddTCBap {
  932.     typeset aprec group groups= xgroupname xgid
  933.     typeset acctname=$1 encpass=$2 uid=$3 gid=$4 GCOS=$5 UserHome=$6
  934.     typeset FullShell=$7
  935.  
  936.     if [ $# -lt 7 ]; then
  937.     print -u2 "\007\007\007ERROR in AddTCBap: only $# args passed:"
  938.     print -u2 "acctname=$1:encpass=$2:uid=$3:gid=$4:GCOS=$5:UserHome=$6"
  939.     return 2
  940.     fi
  941.     shift 7
  942.     if [ $# -gt 0 ]; then
  943.     for group; do
  944.         GoodGroup "$group" | read xgroupname xgid
  945.         if [ -z "$xgroupname" ]; then
  946.         print -u2 "Aborting account creation due to bad group."
  947.         return 1
  948.         fi
  949.         groups="$groups
  950. $xgroupname::$xgid:"
  951.     done
  952.     groups="$groups
  953. ENDOFGROUPS::0:"
  954.     fi
  955.     aprec="$acctname:x:$uid:$gid:$GCOS:$UserHome:$FullShell
  956. $acctname:u_name=$acctname:u_id#$uid:u_pwd=$encpass:u_type=general:${Retries}u_lock@:chkent:$groups"
  957.     print -r "Record to be processed by /tcb/bin/ap:
  958. $aprec"
  959.     if isfalse test; then
  960.     print -r "$aprec" >> $ap_tmp
  961.     fi
  962.     return 0
  963. }
  964.  
  965. # AddTCB: Add user to password and possibly shadow files
  966. # Run authck -p -s -y if neccessary
  967. # Global data used:
  968. # acctname    user account name
  969. # encpass    encoded password
  970. # uid        numeric uid
  971. # gid        numeric login group id
  972. # GCOS        comment for GCOS field
  973. # HOME        home directory
  974. # SHELL        login shell
  975. # test        nonzero if no actual actions should be taken
  976. # notcb        nonzero if authck shouldn't be run.
  977. #function AddTCB {
  978. #    typeset shadowline passwdline
  979. #
  980. #    unset shadowline
  981. #    if [ -f /etc/shadow ]; then
  982. #    shadowline="$acctname:$encpass:::"
  983. #    encpass=x
  984. #    fi
  985. #
  986. #    passwdline="$acctname:$encpass:$uid:$gid:$GCOS:$HOME:$SHELL"
  987. #
  988. #    # Add user to /etc/passwd
  989. #    print "Adding user \"$acctname\" to /etc/passwd with the following line:"
  990. #    print -r "$passwdline"
  991. #    isfalse test && print -r "$passwdline" >> /etc/passwd
  992. #
  993. #    # Add user to /etc/shadow
  994. #    if [ -n "$shadowline" ]; then
  995. #    print \
  996. #    "Adding user \"$acctname\" to /etc/shadow with the following line:"
  997. #    print -r "$shadowline"
  998. #    isfalse test && print -r "$shadowline" >> /etc/shadow
  999. #    fi
  1000. #    isfalse test && isfalse notcb && 
  1001. #    [ -x /tcb/bin/authck ] && /tcb/bin/authck -p -s -y
  1002. #}
  1003.  
  1004. # Usage: AddToGroups group ...
  1005. # Globals used: test
  1006. #function AddToGroups {
  1007. #    typeset group IFS=,
  1008. #
  1009. #    for group; do
  1010. #    # Add user to /etc/group
  1011. #    print "Adding user \"$acctname\" to group \"$group\" in /etc/group..."
  1012. #    isfalse test && {
  1013. #    Test_group
  1014. #    cp /etc/group /etc/group-
  1015. #    sed "/^$group:/s/\(.*\)/\1,$acctname/" /etc/group- > /etc/group
  1016. #    }
  1017. #    done
  1018. #}
  1019.  
  1020. # Add user to system
  1021. # Globals used: Account Mkuser* ADDRESS test uid AccountVars
  1022. # Globals changed: BadAccounts MILind MkuserInitLines UIDsUsed
  1023. # Script is aborted if mkuser files for selected shell cannot be read.
  1024. # 1 is returned if invalid values are given for account params.
  1025. function MakeUser {
  1026.     istrue debug && set -x
  1027.     typeset GCOS Entry OIFS UserHome var
  1028.     typeset $AccountVars
  1029.     typeset MkuserVars="HOME OTHER_GROUPS HOMEMODE MAILMODE PROFMODE SHELL"
  1030.     typeset $MkuserVars ret
  1031.     typeset -i i
  1032.     unset uid
  1033.  
  1034.     # Check/modify values in Account
  1035.     if CheckParams; then :; else
  1036.     nGet -n Account acctname acctname
  1037.     BadAccounts="$BadAccounts $acctname"
  1038.     return 1
  1039.     fi
  1040.     for var in $AccountVars; do
  1041.     nGet -n Account $var $var
  1042.     ret=$?
  1043.     if [[ $var != @(phone_num|uid|address) ]] && eval [ -z \"\$$var\" ]
  1044.     then
  1045.         print -u2 "\007\007\007ERROR in MakeUser: null value for" \
  1046.         "required parm $var; nGet returned $ret"
  1047.         return 2
  1048.     fi
  1049.     done
  1050.  
  1051.     for var in $MkuserVars; do
  1052.     nGet -n Mkuser $var $var
  1053.     done
  1054.  
  1055.     # Set SHELL for given shell.
  1056.     # If HOME is also given, it overrides home set for general accounts.
  1057.     # Other variables may also be set here and override those
  1058.     # set in /etc/default/mkuser etc.
  1059.     # (MIN_SUGGEST_UID, LOGIN_GROUP, HOMEMODE, etc.)
  1060.     # This must come after GetAccountInfo or nset_vars Account "@"
  1061.     # so that $shell will be set.
  1062.     nUnset ShellVars $MkuserVars
  1063.     SetMkuserDef ShellVars /usr/lib/mkuser/$shell/mkuser.defs || return 2
  1064.     for var in $MkuserVars; do
  1065.     nGet -n ShellVars $var $var
  1066.     done
  1067.  
  1068.     # Must do this after mkuser.defs is read, since the shell may have
  1069.     # a special min uid, etc.
  1070.     if [ -z "$uid" ]; then
  1071.     uid=$(SetUID $MIN_SUGGEST_UID $UIDsUsed)
  1072.     UIDsUsed="$UIDsUsed $uid"
  1073.     print -u2 "User assigned uid $uid."
  1074.     fi
  1075.     UserHome=$HOME/$acctname
  1076.     [ -n "$phone_num" ] && GCOS="$name,$phone_num" || GCOS=$name
  1077.  
  1078.     AddTCBap $acctname "$encpass" $uid $gid "$GCOS" $UserHome $SHELL \
  1079.     $OTHER_GROUPS || return $?
  1080.  
  1081.     # Make home directory for user & put shell files in it
  1082.     # mkuser.init usage:
  1083.     # sh mkuser.init user group home homemode mailmode profmode
  1084.     # homemode, mailmode and profmode are the modes to chmod 
  1085.     # user's home dir, mail spool file, and login files to.
  1086.     MkuserInitLines[MILind]=\
  1087. "$shell $acctname $groupname $UserHome $HOMEMODE $MAILMODE $PROFMODE"
  1088.     let MILind+=1
  1089.  
  1090.     # print address for address file
  1091.     if [ -n "$address" ]; then 
  1092.     # Address database uses '+' for record separation, and unfortunately
  1093.     # recognizes them anywhere.  So, get rid of all + chars in record.
  1094.     Entry=\
  1095. "$(print -r -- "$name\n$address\n$phone_num\n$acctname@`hostname`\n" |
  1096.     tr -d +)+"
  1097.     OIFS=$IFS
  1098.     IFS="$IFS:"
  1099.     set -- $ADDRESS
  1100.     IFS=$OIFS
  1101.     if istrue DoAddr && isfalse test; then
  1102.         if [ -n "$1" ]; then
  1103.         print "$Entry" >> $1
  1104.         elif [ -f $addrdef ]; then
  1105.         print "$Entry" >> $addrdef
  1106.         else
  1107.         print -u3 "
  1108. Address file entry:
  1109.  
  1110. $Entry
  1111. "
  1112.         fi
  1113.     fi
  1114.     fi
  1115.     return 0
  1116. }
  1117.  
  1118. function ProcReq {
  1119.     istrue debug && set -x
  1120.     typeset -i LNum=1
  1121.  
  1122.     # Start from scratch for each user
  1123.     nUnset Account $allAccountVars
  1124.     while [ LNum -le NLines ]; do
  1125.     print -r "${Lines[LNum]}"
  1126.     let LNum+=1
  1127.     done | nset_vars Account
  1128.     # Use values in Account to build user
  1129.     # Return MakeUser's return status
  1130.     MakeUser
  1131. }
  1132.  
  1133. # Start of main program
  1134.  
  1135. # Namespace Account stores account parameters.
  1136. # Normal vars are: acctname, name, shell, password, uid, group, address,
  1137. # phone_num, gid, groupname, encpass
  1138.  
  1139. unset ENV
  1140.  
  1141. PATH=$PATH:/usr/bin:/bin:/usr/local/admin
  1142. addrdef=/usr/local/public/address
  1143. Name=${0##*/}
  1144. Usage="Usage: $Name [-anht] [-r<retries>] [request-file]"
  1145.  
  1146. typeset -i test=0 debug=0 numflags=0 Interactive MILind=0 DoAddr=1
  1147. AccountVars="uid acctname phone_num name gid groupname address shell encpass"
  1148. allAccountVars="$AccountVars group password"
  1149.  
  1150. # Read /etc/default/(mkuser or authsh) for default values:
  1151. # HOME HOMEMODE PROFMODE MAILMODE LOGIN_GROUP 
  1152. # MIN_SUGGEST_GID MIN_SUGGEST_UID OTHER_GROUPS
  1153. # Do this before DoFlags so that if help is asked for,
  1154. # the default values of certain params can be included in it.
  1155. SetMkuserDef -d Mkuser /etc/default/mkuser /etc/default/authsh || exit 1
  1156.  
  1157. # Used by Help
  1158. nGet Mkuser MIN_SUGGEST_UID MIN_SUGGEST_UID
  1159. # Used by Help and Test_group
  1160. nGet Mkuser LOGIN_GROUP LOGIN_GROUP
  1161.  
  1162. # Set this before running DoFlags so that -n can override it.
  1163. [ ! -t 0 ]
  1164. Interactive=$?
  1165.  
  1166. DoFlags    "$@"    # interpret command line args
  1167. shift $?
  1168. istrue debug && set -x
  1169. if [ $# -gt 0 ]; then    # filename given on command line?
  1170.     Interactive=0
  1171.     exec < "$1" || exit 1
  1172. fi
  1173.  
  1174. # Informational messages are printed to fd 1 (stdout).
  1175. # If error output is a tty or -t is given, 
  1176. # make them go to fd 2 (stderr).
  1177. # Otherwise make them go to /dev/null.
  1178. # fd 3 is set to stdout so that address line can be written to it.
  1179. # fd 2 is not changed; error and interactive message go to it.
  1180. [ -t 2 -o debug -ne 0 ] && exec 3>&1 1>&2 || exec 3>&1 1>/dev/null
  1181.  
  1182. mktempfile adduser. || {
  1183.     print -u2 "$Name: could not make temp file.  Exiting."
  1184.     exit 1
  1185. }
  1186. ap_tmp=$mktempfile_ret
  1187. trap 'rm -f $ap_tmp' EXIT
  1188.  
  1189. # Store values in Account*
  1190. if ((Interactive)); then
  1191.     # Prompt for vars & sets them.
  1192.     GetAccountInfo
  1193.     # Use values in Account* to build user
  1194.     MakeUser
  1195.     [ $? = 2 ] && exit 1
  1196. else
  1197.     # Non-interactive; set account vars by std input or given filename.
  1198.     typeset -i NLines=0
  1199.  
  1200.     # Even raw read removes leading & trailing spaces.
  1201.     # Leading is ok, but not trailing (might be part of a password).
  1202.     # So, add a : to the end of each line, then get rid of it.
  1203.     # After each group of lines that contain an '=', pass record to ProcRec.
  1204.     sed 's/$/:/' "$@" | while read -r line; do
  1205.     if [[ "$line" = *=* ]]; then
  1206.         let NLines+=1
  1207.         Lines[NLines]=${line%:}
  1208.     else
  1209.         if [ NLines -gt 0 ]; then
  1210.         ProcReq
  1211.         [ $? = 2 ] && exit 1
  1212.         NLines=0
  1213.         fi
  1214.     fi
  1215.     done
  1216.     if [ NLines -gt 0 ]; then
  1217.     ProcReq
  1218.     fi
  1219. fi
  1220.  
  1221. if isfalse test && [ -s "$ap_tmp" ]; then
  1222.     /tcb/bin/ap -v -r -f $ap_tmp || {
  1223.       print -u2 "\007\007\007ERROR: ap failed!  Aborting."
  1224.       print -u2 "ap file left in $ap_tmp"
  1225.       trap - EXIT
  1226.       exit 1
  1227.     }
  1228.  
  1229.  
  1230.     print -u2 "Creating home dirs & sending newuser mail..."
  1231.     # Input is made /dev/null because some mkuser scripts ask questions
  1232.     for line in "${MkuserInitLines[@]}"; do
  1233.     set -- $line
  1234.     shell=$1
  1235.     shift
  1236.     /bin/sh /usr/lib/mkuser/$shell/mkuser.init "$@"
  1237.     done < /dev/null
  1238. fi
  1239.  
  1240. [ -n "$BadAccounts" ] && print -u2 "Account creation failed for:$BadAccounts"
  1241. [ -n "$Truncated" ] && print -u2 "Passwords were truncated for:$Truncated"
  1242.  
  1243. # The EXIT trap will remove the tmpfile
  1244.